#include "gst_enesim_sink.h"

GST_DEBUG_CATEGORY_EXTERN (enesim_sink_debug);
#define GST_CAT_DEFAULT enesim_sink_debug

GST_BOILERPLATE (GstEnesimSink, gst_enesim_sink, GstVideoSink, GST_TYPE_VIDEO_SINK);

/* ElementFactory information */
static const GstElementDetails gst_enesim_sink_details =
GST_ELEMENT_DETAILS ("Enesim Surface Sink",
    "Sink/Video",
    "An Enesim Surface based sink",
    "Jorge 'turran' Zapata <jorgeluis.zapata@gmail.com>");

enum
{
  PROP_0,
  PROP_FORMAT,
  PROP_WIDTH,
  PROP_HEIGHT,
  PROP_POOL,
};

enum
{
  SIGNAL_BUFFER_DISPLAY,
  LAST_SIGNAL
};

static guint gst_enesim_sink_signals[LAST_SIGNAL] = { 0 };

/* Default template - initiated with class struct to allow gst-register to work
   without X running */
static GstStaticPadTemplate gst_enesim_sink_sink_template_factory =
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/x-raw-rgb, "
       "framerate = (fraction) [ 0, MAX ], "
        "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
);

static inline int _get_mask(uint8_t offset, uint8_t len)
{
  int m;
  m = ((1 << len) - 1) << offset;
  return m;
}

static gboolean
gst_enesim_sink_setup(GstEnesimSink *thiz)
{
  enesim_init ();
  return TRUE;
}

static void
gst_enesim_sink_cleanup(GstEnesimSink *thiz)
{
  enesim_shutdown ();
}

static GstCaps *
gst_enesim_sink_getcaps (GstBaseSink * bsink)
{
  GstEnesimSink * thiz = GST_ENESIM_SINK (bsink);
  GstCaps * caps;
  GstStructure *structure;
  Eina_Bool premul;
  uint8_t aoffset, alen;
  uint8_t roffset, rlen;
  uint8_t goffset, glen;
  uint8_t boffset, blen;
  uint8_t depth;
  int amask;
  int rmask;
  int gmask;
  int bmask;

  /* check the format set on the property */
  switch (thiz->format) {
    case ENESIM_BUFFER_FORMAT_ARGB8888_PRE:
    case ENESIM_BUFFER_FORMAT_ARGB8888:
    break;

    default:
    return NULL;
    break;
  }

  /* set the width/height */
  if (!enesim_buffer_format_rgb_components_to(thiz->format,
    &aoffset, &alen,
    &roffset, &rlen,
    &goffset, &glen,
    &boffset, &blen,
    &premul)) {
    return NULL;
  }

  rmask = _get_mask (roffset, rlen);
  gmask = _get_mask (goffset, glen);
  amask = _get_mask (aoffset, alen);
  bmask = _get_mask (boffset, blen);

  /* FIXME we miss to separate between depth and bpp */
  /* the depth */
  depth = enesim_buffer_format_rgb_depth_get(thiz->format);

  /* FIXME we needed to swap the colors, the endianess is not correct */
  caps = gst_caps_new_empty ();
  structure = gst_structure_new ("video/x-raw-rgb",
      "bpp", G_TYPE_INT, depth,
      "depth", G_TYPE_INT, depth,
      "endianness", G_TYPE_INT, G_BIG_ENDIAN,
      "alpha_mask", G_TYPE_INT, bmask,
      "red_mask", G_TYPE_INT, gmask,
      "green_mask", G_TYPE_INT, rmask,
      "blue_mask", G_TYPE_INT, amask,
      "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);

  if (GST_VIDEO_SINK_WIDTH (thiz) < 0) {
    gst_structure_set (structure, "width", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL);
  } else {
    gst_structure_set (structure, "width", G_TYPE_INT, GST_VIDEO_SINK_WIDTH (thiz), NULL);
  }

  if (GST_VIDEO_SINK_HEIGHT (thiz) < 0) {
    gst_structure_set (structure, "height", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL);
  } else {
    gst_structure_set (structure, "height", G_TYPE_INT, GST_VIDEO_SINK_HEIGHT (thiz), NULL);
  }

  caps = gst_caps_new_full (structure, NULL);
  GST_DEBUG_OBJECT (thiz, "Getting caps %" GST_PTR_FORMAT, caps);

  return caps;
}

static gboolean
gst_enesim_sink_setcaps (GstBaseSink * bsink, GstCaps * caps)
{
  GstEnesimSink * thiz = GST_ENESIM_SINK (bsink);
  GstStructure *s;

  GST_DEBUG_OBJECT (bsink, "Setting capscaps = %" GST_PTR_FORMAT, caps);
  /* pick the width and height */
  s = gst_caps_get_structure (caps, 0);
  gst_structure_get_int (s, "width", &thiz->final_width);
  gst_structure_get_int (s, "height", &thiz->final_height);

  return TRUE;
}

static GstFlowReturn
gst_enesim_sink_show_frame (GstBaseSink * bsink, GstBuffer * buffer)
{
  GstEnesimSink *thiz = GST_ENESIM_SINK (bsink);
  GstEnesimBuffer *thiz_buffer;

  /* TODO check if the buffer is one of our own buffers */
  /* TODO if so, pass the buffer associated with it */
  /* if not, create a new buffer from the data */
  thiz_buffer = gst_enesim_buffer_new_from_buffer (buffer, thiz->format, thiz->final_width, thiz->final_height, thiz->pool);

  g_signal_emit (thiz, gst_enesim_sink_signals[SIGNAL_BUFFER_DISPLAY], 0, thiz_buffer->b);
  if (thiz->last_buffer) {
    gst_buffer_unref (thiz->last_buffer);
  }
  thiz->last_buffer = thiz_buffer;

  return GST_FLOW_OK;
}

static GstStateChangeReturn
gst_enesim_sink_change_state (GstElement * element, GstStateChange transition)
{
  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
  GstEnesimSink *thiz;

  thiz = GST_ENESIM_SINK (element);

  switch (transition) {
    case GST_STATE_CHANGE_NULL_TO_READY:
      if (!gst_enesim_sink_setup (thiz))
        return GST_STATE_CHANGE_FAILURE;
      break;
    default:
      break;
  }

  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);

  switch (transition) {
    case GST_STATE_CHANGE_READY_TO_NULL:
      gst_enesim_sink_cleanup (thiz);
      break;
    default:
      break;
  }

  return ret;
}

static void
gst_enesim_sink_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstEnesimSink *thiz;

  thiz = GST_ENESIM_SINK (object);
  switch (prop_id) {
    case PROP_WIDTH:
      GST_VIDEO_SINK_WIDTH (thiz) = g_value_get_int (value);
      break;
    case PROP_HEIGHT:
      GST_VIDEO_SINK_HEIGHT (thiz) = g_value_get_int (value);
      break;
    case PROP_FORMAT:
      thiz->format = g_value_get_int (value);
      break;
    case PROP_POOL:
      thiz->pool = g_value_get_pointer (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_enesim_sink_get_property (GObject * object, guint prop_id, GValue * value,
    GParamSpec * pspec)
{
  GstEnesimSink *thiz;

  thiz = GST_ENESIM_SINK (object);
  switch (prop_id) {
    case PROP_WIDTH:
      g_value_set_int (value, GST_VIDEO_SINK_WIDTH (thiz));
      break;
    case PROP_HEIGHT:
      g_value_set_int (value, GST_VIDEO_SINK_HEIGHT (thiz));
      break;
    case PROP_FORMAT:
      g_value_set_int (value, thiz->format);
      break;
    case PROP_POOL:
      g_value_set_pointer (value, thiz->pool);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_enesim_sink_base_init (gpointer g_class)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);

  gst_element_class_set_details (element_class, &gst_enesim_sink_details);
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&gst_enesim_sink_sink_template_factory));
}

static void
gst_enesim_sink_class_init (GstEnesimSinkClass *klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;
  GstBaseSinkClass *gstbasesink_class;

  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;
  gstbasesink_class = (GstBaseSinkClass *) klass;

  parent_class = g_type_class_peek_parent (klass);

  gobject_class->set_property = gst_enesim_sink_set_property;
  gobject_class->get_property = gst_enesim_sink_get_property;

  //gobject_class->finalize = gst_enesim_sink_finalize;

  gstelement_class->change_state =
      GST_DEBUG_FUNCPTR (gst_enesim_sink_change_state);

  gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_enesim_sink_getcaps);
  gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_enesim_sink_setcaps);
  gstbasesink_class->preroll = GST_DEBUG_FUNCPTR (gst_enesim_sink_show_frame);
  gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_enesim_sink_show_frame);

  /* Register signals */
  gst_enesim_sink_signals[SIGNAL_BUFFER_DISPLAY] =
      g_signal_new ("buffer-display", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET (GstEnesimSinkClass, buffer_display), NULL, NULL,
      g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
  /* Register properties */
  g_object_class_install_property (gobject_class, PROP_WIDTH,
      g_param_spec_int ("width", "Width",
          "Wait of the destination surface",
          -1,
          G_MAXINT,
          -1,
          G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class, PROP_HEIGHT,
      g_param_spec_int ("height", "Height",
          "Height of the destination surface",
          -1,
          G_MAXINT,
          -1,
          G_PARAM_READWRITE));
  /* FIXME we should make this an enum later with all the Enesim_Buffer_Format's */
  g_object_class_install_property (gobject_class, PROP_FORMAT,
      g_param_spec_int ("format", "Format",
          "Format of the destination surface",
          0,
          ENESIM_BUFFER_FORMATS,
          ENESIM_BUFFER_FORMAT_ARGB8888_PRE,
          G_PARAM_READWRITE));
}

static void
gst_enesim_sink_init (GstEnesimSink *thiz, GstEnesimSinkClass *klass)
{
  /* default values */
  thiz->format = ENESIM_BUFFER_FORMAT_ARGB8888_PRE;
  GST_VIDEO_SINK_WIDTH (thiz) = -1;
  GST_VIDEO_SINK_HEIGHT (thiz) = -1;
}
